import * as THREE from "three"
import Stats from "three/examples/jsm/libs/stats.module.js";
import { OrbitControls } from "../lib/OrbitControls"
import { deltaTime, elapsedTime } from "./constants";
import { fitCameraToObject3D } from "../utils/others";

const zeroVec3 = new THREE.Vector3();



export class SketchBase {

    /**@type { THREE.Scene } 场景 */
    #scene;

    /**@type { THREE.WebGLRenderer } 渲染器 */
    #renderer;

    /**@type { THREE.PerspectiveCamera|THREE.OrthographicCamera } 相机 */
    #camera;

    /**@type { THREE.PerspectiveCamera } 透视相机 */
    #perspectiveCamera;

    /**@type {THREE.OrthographicCamera} 正交相机  */
    #orthoCamera;

    /**@type { OrbitControls } 当前相机控制器 */
    #controls;

    /**@type { THREE.PMREMGenerator } 环境生成器 */
    #pmremGenerator

    /**@type { HTMLElement } 当前canvas所在的 HTMLElement 对象 */
    #targetElement;

    /**@type { Array<( width:number,height:number ) => void>} 窗口大小发生改变，执行的任务队列 */
    #onresizeQueue;

    /**@type { Array< ( param: this ) => void>} 渲染时，执行的任务队列 */
    #onRenderQueue;

    /**@type { Array< ( param: this ) => void>} 控制器开始变换时，执行的任务队列 */
    #onControlsStartQueue;

    /**@type { Array< ( param: this ) => void>} 控制器变换时，执行的任务队列 */
    #onControlsChangeQueue;

    /**@type { Array< ( param: this ) => void>} 控制器停止变换时，执行的任务队列 */
    #onControlsEndQueue;

    /**@type { Array< ( param: this ) => void>} 相机切换时，执行的任务队列 */
    #onCameraChangeQueue;

    /**@type {number} 正交相机自适应比例，默认为 0.7 */
    ortho_resize_scale = 0.7;

    /**@type { THREE.Scene } 场景 */
    get scene() {

        return this.#scene;

    }

    set scene(newScene) {

        if (newScene.isScene) {

            this.#scene = newScene;

        }

    }

    /**@type { THREE.PerspectiveCamera|THREE.OrthographicCamera } 透视相机或正交相机 */
    get camera() {

        return this.#camera;

    }

    /**@type { THREE.PerspectiveCamera } 基础透视相机 */
    get perspectiveCamera() {

        return this.#perspectiveCamera;

    }

    /**@type { THREE.OrthographicCamera } 基础正交相机 */
    get orthoCamera() {

        return this.#orthoCamera;

    }

    set camera(newCamera) {

        if (!newCamera.isCamera) return;

        this.#camera = newCamera;

        if (this.#camera.isOrthographicCamera) {

            this.#scene.background = null;

        } else {

            // this.#scene.background = SunnyTexture;

        }

        this.#onDefaultCameraChange(newCamera);

        this.#onCameraChangeQueue.forEach(fn => fn(this));

    }

    /**@type { THREE.WebGLRenderer } 渲染器 */
    get renderer() {

        return this.#renderer;

    }

    /**@type { OrbitControls } 相机控制器 */
    get controls() {

        return this.#controls;

    }

    /**@type { HTMLElement } 当前canvas所在的 HTMLElement 对象 */
    get targetElement() {

        return this.#targetElement;

    }

    /**@param {boolean} bool 设置缓存，默认为false*/
    set cache(bool) {

        THREE.Cache.enabled = !!bool;

    }

    /** 控制器是否正在改变，希望大多数的交互计算可以停止，当控制器正在改变的时候。 */
    #controlsIsChanging = false;

    /** 控制器是否正在改变，希望大多数的交互计算可以停止，当控制器正在改变的时候。 */
    get controlsIsChanging() {

        return this.#controlsIsChanging;

    }

    /**@type {(_this:this)=>void} 自定义渲染函数 */
    customRender;

    /**
     * @constructor
     * @param {HTMLElement} targetElement
     * @param {THREE.WebGLRendererParameters} parameters
     */
    constructor(targetElement, parameters = {}) {

        this.cache = false;

        this.clock = new THREE.Clock();

        this.#onresizeQueue = [];

        this.#onRenderQueue = [];

        this.#onControlsStartQueue = [];

        this.#onControlsChangeQueue = [];

        this.#onControlsEndQueue = [];

        this.#onCameraChangeQueue = [];

        this.#targetElement = targetElement;

        this.#scene = new THREE.Scene();

        const { clientWidth, clientHeight } = targetElement;

        // create camera

        const camera = this.#camera = this.#perspectiveCamera = new THREE.PerspectiveCamera(55, clientWidth / clientHeight, 0.01, 10000);
        camera.position.set(100, 100, 100);
        camera.lookAt(zeroVec3);

        // create controls
        this.#controls = this.initControls(camera);

        this.#orthoCamera = new THREE.OrthographicCamera();
        this.setOrthCameraSize(window.innerWidth, window.innerHeight, this.ortho_resize_scale);
        this.#orthoCamera.position.set(0, 100, 0);
        this.#orthoCamera.lookAt(0, 0, 0);

        // canvas
        parameters.canvas = parameters.canvas ?? document.createElement("canvas")
        targetElement.appendChild(parameters.canvas);
        targetElement.oncontextmenu = e => false;

        // create renderer
        const renderer = this.#renderer = new THREE.WebGLRenderer(parameters);
        renderer.setSize(clientWidth, clientHeight);
        renderer.outputColorSpace = THREE.SRGBColorSpace;
        renderer.toneMapping = THREE.ACESFilmicToneMapping;
        renderer.shadowMap.enabled = true;
        renderer.shadowMap.type = THREE.PCFSoftShadowMap;
        renderer.setPixelRatio(window.devicePixelRatio);
        renderer.info.autoReset = false;
        renderer.localClippingEnabled = true;
        renderer.domElement.removeAttribute("data-engine");

        this.#pmremGenerator = new THREE.PMREMGenerator(renderer);
        this.#pmremGenerator.compileEquirectangularShader();

        window.addEventListener("resize", () => {

            const { clientWidth, clientHeight } = targetElement;

            const perspectiveCamera = this.#perspectiveCamera;

            perspectiveCamera.aspect = clientWidth / clientHeight;

            perspectiveCamera.updateProjectionMatrix();

            const orthoCamera = this.#orthoCamera;

            if (orthoCamera) {

                const scale = this.ortho_resize_scale;
                orthoCamera.left = -clientWidth * scale;
                orthoCamera.right = clientWidth * scale;
                orthoCamera.top = clientHeight * scale;
                orthoCamera.bottom = -clientHeight * scale;
                orthoCamera.updateProjectionMatrix();

            }

            renderer.setSize(clientWidth, clientHeight);

            renderer.setPixelRatio(window.devicePixelRatio);

            this.#onresizeQueue.forEach(fn => fn(clientWidth, clientHeight));

        });

    }

    /** 控制器开始变化时，更新状态 */
    #onControlsStart = () => {

        this.#controlsIsChanging = true;

        this.#onControlsStartQueue.forEach(cb => {

            cb(this);

        });

    };

    /** 控制器变化时，执行函数 */
    #onControlsChange = () => {

        this.#onControlsChangeQueue.forEach(cb => {

            cb(this);

        });

    };

    /** 控制器结束变化时，更新状态，并执行相关回调。 */
    #onControlsEnd = () => {

        this.#controlsIsChanging = false;

        this.#onControlsEndQueue.forEach(cb => {

            cb(this);

        });

    };

    /**
     * 设置正交相机渲染视图尺寸
     * @param {number} width 宽
     * @param {number} height 高
     * @param {number} scale 系数
     */
    setOrthCameraSize(width, height, scale = 1) {

        const camera = this.#orthoCamera;
        camera.left = -width * scale;
        camera.right = width * scale;
        camera.top = height * scale;
        camera.bottom = -height * scale;
        camera.updateProjectionMatrix();

    }

    /**
     * 设置环境
     * @param {THREE.Color|THREE.Texture|THREE.Scene} environment 
     */
    setEnvironment(environment) {

        let envMap = null

        if (environment.isColor) {

            const envScene = new THREE.Scene()
            envScene.background = new THREE.Color(environment)
            envMap = this.#pmremGenerator.fromScene(envScene).texture;

        } else if (environment.isTexture) {

            envMap = this.#pmremGenerator.fromEquirectangular(texture).texture;
            this.#pmremGenerator.dispose();

        } else if (environment.isScene) {

            envMap = this.#pmremGenerator.fromScene(environment).texture;

        }

        this.scene.environment = envMap;

    }

    /**
     * 移动相机到对象
     * @param { THREE.Object3D } object
     */
    fitCameraToObject3D(object) {

        fitCameraToObject3D(object, this.camera, this.controls)

    }

    /** 初始化性能监视器 */
    initStats() {

        const stats = new Stats();

        document.body.appendChild(stats.dom);

        this.addToRenderQueue(() => stats.update());

        return stats;

    }

    /**
     * 初始化坐标系辅助线
     * @param {number} size 
     * @returns 
     */
    initAxesHelper(size = 100) {

        const axesHelper = new THREE.AxesHelper(size);

        this.#scene.add(axesHelper);

        return axesHelper;

    }
    /**
     * 初始化网格辅助线
     * @param {number} size 
     * @param {number} divisions
     * @param {THREE.Color} color1 
     * @param {THREE.Color} color2 
     * @returns 
     */
    initGridHelper(size = 100, divisions = 10, color1, color2) {

        const gridHelper = new THREE.GridHelper(size, divisions, color1, color2);

        this.#scene.add(gridHelper);

        return gridHelper;

    }



    /**
     * 保存当前视角
     */
    saveView() {

        const { x, y, z } = this.camera.position;

        const { x: tx, y: ty, z: tz } = this.controls.target;

        return {
            position: {
                x,
                y,
                z
            },
            target: {
                x: tx,
                y: ty,
                z: tz
            }
        };

    }


    /**
     * 将函数添加到任务队列，函数将会每帧执行。
     * @param {(param: this) => void} fn
     */
    addToRenderQueue(fn) {

        const onRenderQueue = this.#onRenderQueue;

        if (onRenderQueue.indexOf(fn) === -1) {

            onRenderQueue.push(fn);

        }

    }

    /**
     * 将函数从任务队列移除。
     * @param {(param: this) => void} fn
     */
    removeFromRenderQueue(fn) {

        const onRenderQueue = this.#onRenderQueue;

        const index = onRenderQueue.indexOf(fn);

        if (index !== -1) {

            onRenderQueue.splice(index, 1);

        }

    }

    /**
     * 将函数添加到任务队列，函数将在窗口尺寸发生变化时执行。
     * @param {(width: number, height: number) => void} fn
     */
    addToResizeQueue(fn) {

        const resizeQueue = this.#onresizeQueue;

        if (resizeQueue.indexOf(fn) === -1) {

            resizeQueue.push(fn);

        }

    }

    /**
     * 将函数从任务队列移除。
     * @param {(width: number, height: number) => void} fn
     */
    removeFromResizeQueue(fn) {

        const resizeQueue = this.#onresizeQueue;

        const index = resizeQueue.indexOf(fn);

        if (index !== -1) {

            resizeQueue.splice(index, 1);

        }

    }

    /**
     * 将函数添加到任务队列，函数将在控制器开始变化时执行。
     * @param {(param: this) => void} fn
     */
    addToControlsStartQueue(fn) {

        const onControlsStartQueue = this.#onControlsStartQueue;

        if (onControlsStartQueue.indexOf(fn) === -1) {

            onControlsStartQueue.push(fn);

        }

    }

    /**
     * 将函数从任务队列移除。
     * @param {(param: this) => void} fn
     */
    removeFromControlsStartQueue(fn) {

        const onControlsStartQueue = this.#onControlsStartQueue;

        const index = onControlsStartQueue.indexOf(fn);

        if (index !== -1) {

            onControlsStartQueue.splice(index, 1);

        }

    }

    /**
     * 将函数添加到任务队列，函数将在控制器变化时执行。
     * @param {(param: this) => void} fn
     */
    addToControlsChangeQueue(fn) {

        const onControlsChangeQueue = this.#onControlsChangeQueue;

        if (onControlsChangeQueue.indexOf(fn) === -1) {

            onControlsChangeQueue.push(fn);

        }

    }

    /**
     * 将函数从任务队列移除。
     * @param {(param: this) => void} fn
     */
    removeFromControlsChangeQueue(fn) {

        const onControlsChangeQueue = this.#onControlsChangeQueue;

        const index = onControlsChangeQueue.indexOf(fn);

        if (index !== -1) {

            onControlsChangeQueue.splice(index, 1);

        }

    }

    /**
     * 将函数添加到任务队列，函数将在控制器变化结束时执行。
     * @param {(param: this) => void} fn
     */
    addToControlsEndQueue(fn) {

        const onControlsEndQueue = this.#onControlsEndQueue;

        if (onControlsEndQueue.indexOf(fn) === -1) {

            onControlsEndQueue.push(fn);

        }

    }

    /**
     * 将函数从任务队列移除。
     * @param {(param: this) => void} fn
     */
    removeFromControlsEndQueue(fn) {

        const onControlsEndQueue = this.#onControlsEndQueue;

        const index = onControlsEndQueue.indexOf(fn);

        if (index !== -1) {

            onControlsEndQueue.splice(index, 1);

        }

    }

    /**
     * 将函数添加到任务队列，函数将在相机切换时执行。
     * @param {(param: this) => void} fn
     */
    addToCameraChangeQueue(fn) {

        const onCameraChangeQueue = this.#onCameraChangeQueue;

        if (onCameraChangeQueue.indexOf(fn) === -1) {

            onCameraChangeQueue.push(fn);

        }

    }

    /**
     * 将函数从任务队列移除。
     * @param {(param: this) => void} fn
     */
    removeFromCameraChangeQueue(fn) {

        const onCameraChangeQueue = this.#onCameraChangeQueue;

        const index = onCameraChangeQueue.indexOf(fn);

        if (index !== -1) {

            onCameraChangeQueue.splice(index, 1);

        }

    }

    #onDefaultCameraChange(camera) {

        this.#disposeControls();

        this.#controls = this.initControls(camera);

    }

    /**
     * 初始化控制器
     * @param {THREE.PerspectiveCamera|THREE.OrthographicCamera} camera 相机
     */
    initControls(camera) {

        const controls = new OrbitControls(camera, this.#targetElement);

        controls.target.set(0, 0, 0);

        controls.enableDolly = false;

        // controls.minDistance = 20;

        controls.maxDistance = 5000;

        controls.data = {};

        if (camera.isOrthographicCamera) {

            controls.enableRotate = false;

        }

        controls.addEventListener("start", this.#onControlsStart);

        controls.addEventListener("end", this.#onControlsEnd);

        controls.addEventListener("change", this.#onControlsChange);

        return controls;

    }

    /** 释放控制器事件监听 */
    #disposeControls() {

        this.#controls.removeEventListener("start", this.#onControlsStart);

        this.#controls.removeEventListener("end", this.#onControlsEnd);

        this.#controls.removeEventListener("change", this.#onControlsChange);

        this.#controls.dispose();

    }

    /** 打印当前内存信息 */
    logMemory() {

        this.#renderer.info.reset();

        console.log(this.#renderer.info);

    }

    /** 开始渲染 */
    start() {

        this.renderer.setAnimationLoop(this.render);

    }

    /** 停止渲染 */
    stop() {

        this.renderer.setAnimationLoop(null);

    }

    /** 渲染函数 */
    render = () => {

        if (this.customRender !== void 0) {

            this.customRender();

        } else {

            this.#renderer.render(this.#scene, this.#camera);

        }

        this.#updateTime(this.clock.getDelta())

        this.#controls.update();

        this.#onRenderQueue.forEach(fn => fn(this));

    };

    /**
     * 更新全局时间
     * @param {number} delta 
     */
    #updateTime = (delta) => {

        deltaTime.value = delta

        elapsedTime.value += delta

    }

    /**
     * 设置自定义渲染函数
     * @param {()=>void} customRender
     */
    setCustomRender(customRender) {

        if (typeof customRender === "function") {

            this.customRender = customRender;

        } else {

            this.customRender = void 0;

            console.warn("customRender is undefined , will render with defaultRender");

        }

    }

}

const _$vec3 = new THREE.Vector3();

/**
 * @param {THREE.Sphere} sphere 将向量限制在球内
 */
THREE.Vector3.prototype.clampSphere = function (sphere) {

    if (this.distanceTo(sphere.center) > sphere.radius) {

        _$vec3.subVectors(this, sphere.center).normalize();

        _$vec3.setLength(sphere.radius);

        this.addVectors(sphere.center, _$vec3);

    }

};

